<template>
	<a-tree-select v-bind="getAttrs" @dropdownVisibleChange="handleFetch" @change="handleChange">
		<template #[item]="data" v-for="item in Object.keys($slots)">
			<slot :name="item" v-bind="data || {}"></slot>
		</template>
		<template #suffixIcon v-if="loading">
			<LoadingOutlined spin />
		</template>
	</a-tree-select>
</template>

<script lang="ts">
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
import type { PropType } from 'vue';
import { TreeSelect } from 'ant-design-vue';
import { isArray, isEmpty, isFunction, isNullOrUnDef } from '@/utils/is';
import { get } from 'lodash-es';
import { LoadingOutlined } from '@ant-design/icons-vue';

export default defineComponent({
	name: 'ApiTreeSelect',
	components: { ATreeSelect: TreeSelect, LoadingOutlined },
	props: {
		api: { type: Function as PropType<(arg?: any) => Promise<Recordable>> },
		params: {
			type: [Object, Number, String] as PropType<Recordable | number | string>,
			default: '',
		},
		immediate: { type: Boolean, default: true },
		// 后他返回结果的key
		resultField: { type: String as PropType<string>, default: '' },
		fieldNames: { type: Object },
		afterFetch: { type: Function as PropType<Fn> },
		alwaysLoad: { type: Boolean as PropType<boolean>, default: false },
		lazyLoad: { type: Boolean as PropType<boolean>, default: false },
		// 懒加载参数，比如:根据id查找下一级
		loadDataParamKey: { type: String as PropType<string>, default: 'id' },
		afterLoadData: { type: Function as PropType<Fn> },
	},
	emits: ['options-change', 'change', 'load-data'],
	setup(props, { attrs, emit }) {
		const treeData = ref<Recordable[]>([]);
		const isFirstLoaded = ref<Boolean>(true);
		const loading = ref(false);
		const getAttrs = computed(() => {
			return {
				...(props.api ? { treeData: unref(treeData) } : {}),
				...attrs,
				fieldNames: props.fieldNames,
				...(props.lazyLoad ? { loadData: loadData } : {}),
			};
		});

		function handleChange(...args: any[]) {
			emit('change', ...args);
		}

		watch(
			() => props.params,
			() => {
				!unref(isFirstLoaded) && fetch();
			},
			{ deep: true },
		);

		watch(
			() => props.immediate,
			v => {
				v && isFirstLoaded.value && fetch();
			},
		);

		onMounted(() => {
			props.immediate && !props.alwaysLoad && fetch();
		});

		async function fetch() {
			const { api, afterFetch } = props;
			if (!api || !isFunction(api)) return;
			loading.value = true;
			treeData.value = [];
			let result;
			try {
				result = await api(props.params);
			} catch (e) {
				console.error(e);
			}
			if (afterFetch && isFunction(afterFetch)) {
				result = afterFetch(result);
			}
			loading.value = false;
			if (!result) return;
			if (!isArray(result)) {
				result = get(result, props.resultField);
			}

			treeData.value = (result as Recordable[]) || [];
			isFirstLoaded.value = false;
			emit('options-change', treeData.value);
		}

		async function handleFetch(visible: boolean) {
			if (visible) {
				if (props.alwaysLoad) {
					await fetch();
				} else if (!props.immediate && unref(isFirstLoaded)) {
					await fetch();
					isFirstLoaded.value = false;
				}
			}
		}

		async function loadData(treeNode) {
			if (treeNode.dataRef?.children) {
				return;
			}
			const { api, afterLoadData } = props;
			if (!api || !isFunction(api)) return;

			try {
				let data = await api(treeNode.dataRef[props.loadDataParamKey]);

				if (afterLoadData && isFunction(afterLoadData)) {
					data = afterLoadData(data);
				}

				if (isArray(data)) {
					data.map(item => {
						item.isLeaf = false;
						item.value = item[props.loadDataParamKey];
					});
					treeNode.dataRef.children = data;
					treeData.value = [...treeData.value];
				}
				if (isEmpty(data) || isNullOrUnDef(data)) {
					treeNode.dataRef.isLeaf = true;
				}
			} catch (error) {}
		}

		return { getAttrs, loading, handleChange, handleFetch, loadData };
	},
});
</script>
